// Copyright Gary Evans 2006-2007.

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.Globalization;
using System.IO;

using Tao.OpenGl;

namespace Taumuon.Jabuka.MainApp
{
    public partial class MainOpenGlForm : Form
    {
        #region Member Variables

        private World world = null;
        private List<SceneDescription> sceneDescriptions = null;

        // The time that the simulation has been running for, in
        //  simulation, not realworld time (i.e. not suitable for
        //  measuring frame rates).
        private double simulationTime = 0.0;

        private const string FilePathWithoutExtension = @"c:\screenshots\screenshot";

        // Increment for a fixed time step.
        private static double Increment = 0.1;

        #endregion

        #region Object Lifetime

        /// <summary>
        /// The Application's main form.
        /// </summary>
        public MainOpenGlForm(List<SceneDescription> scenes)
        {
            Debug.Assert(scenes != null && scenes.Count > 0,
                "Should have identified Scenes in the assembly");

            this.sceneDescriptions = scenes;

            InitializeComponent();
            this.simpleOpenGlControl1.InitializeContexts();
        }

        #endregion Object Lifetime

        #region Overrides

        /// <summary>
        /// Handles the Load event of the form.
        /// </summary>
        protected override void OnLoad(EventArgs e)
        {
            SelectScene();
            base.OnLoad(e);
        }

        /// <summary>
        /// Handles the resizing of the form.
        /// </summary>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            if (world != null)
            {
                world.SetView(this.Height, this.Width);
            }
        }

        #endregion Overrides

        #region Event Handlers

        private void simpleOpenGlControl1_Paint(object sender,
                                                PaintEventArgs e)
        {
            if (world != null)
            {
                world.RenderScene();
            }
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (world != null)
            {
                world.UpdateLocations(Increment);
                simulationTime += Increment;
            }
            this.Refresh();
        }

        private void simpleOpenGlControl1_KeyDown(object sender,
            KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Space:
                    if (world != null)
                    {
                        world.ResetScene();
                        simulationTime = 0.0;
                    }
                    break;
                case Keys.Return:
                    SelectScene();
                    break;
                case Keys.S:
                    CaptureCurrentImageAndSave();
                    break;
                default: break;
            }
        }

        #endregion Event Handlers

        #region Private Helper Methods

        private void CreateWorld(SceneDescription sceneDescription)
        {
            this.world = new World((IScene)Activator.CreateInstance(sceneDescription.Type));

            world.InitGl();
            world.SetView(this.Height, this.Width);
        }

        /// <summary>
        /// Displays a dialog to the user with the selected scenes.
        /// </summary>
        private void SelectScene()
        {
            using (SceneChooser sceneChooser = new SceneChooser(this.sceneDescriptions))
            {
                if (sceneChooser.ShowDialog() == DialogResult.OK)
                {
                    CreateWorld(sceneChooser.SelectedDescription);
                }
                else if (world == null)
                {
                    CreateWorld(sceneDescriptions[0]);
                }
                simulationTime = 0.0;
            }
        }

        #region Image Capture
        
        private void CaptureCurrentImageAndSave()
        {
            try
            {
                using (Image image = GetControlImage())
                {
                    AddSimulationTimeToImage(image);
                    string fullPathToFile = string.Concat(FilePathWithoutExtension,
                        simulationTime.ToString("000.00",CultureInfo.InvariantCulture), ".png");

                    try
                    {
                        image.Save(fullPathToFile, System.Drawing.Imaging.ImageFormat.Png);
                    }
                    catch (ArgumentException ex)
                    {
                        string errorMessage = string.Concat("Check that the path to the file exists and is writable: ",
                            fullPathToFile, " . (ex: ", ex.Message, ")");
                        MessageBox.Show(errorMessage, "Error:", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                    catch (System.Runtime.InteropServices.ExternalException ex)
                    {
                        string errorMessage = string.Concat("Check that the path to the file exists and is writable: ",
                            fullPathToFile, " . (ex: ", ex.ToString(), ")");
                        MessageBox.Show(errorMessage, "Error:", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    }
                }
            }
            catch (Exception ex)
            {
                // Any exception causes us to terminate
                MessageBox.Show(ex.Message, "Error:", MessageBoxButtons.OK, MessageBoxIcon.Error);
                throw;
            }
        }

        // From http://www.c-sharpcorner.com/Graphics/ScreenCaptFormMG.asp
        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        private static extern bool BitBlt(
            IntPtr hdcDest, // handle to destination DC
            int nXDest, // x-coord of destination upper-left corner
            int nYDest, // y-coord of destination upper-left corner
            int nWidth, // width of destination rectangle
            int nHeight, // height of destination rectangle
            IntPtr hdcSrc, // handle to source DC
            int nXSrc, // x-coordinate of source upper-left corner
            int nYSrc, // y-coordinate of source upper-left corner
            System.Int32 dwRop // raster operation code
        );

        /// <summary>
        /// Retrieve image the contents of the SimpleOpenGlControl
        /// </summary>
        private Image GetControlImage()
        {
            Image myImage;
            // From http://www.c-sharpcorner.com/Graphics/ScreenCaptFormMG.asp
            using (Graphics graphicsCtrl = simpleOpenGlControl1.CreateGraphics())
            {
                int width = this.simpleOpenGlControl1.ClientRectangle.Width;
                int height = this.simpleOpenGlControl1.ClientRectangle.Height;
                myImage = new Bitmap(width, height, graphicsCtrl);
                using (Graphics graphicsImage = Graphics.FromImage(myImage))
                {
                    IntPtr dcCtrl = graphicsCtrl.GetHdc();
                    IntPtr dcImage = graphicsImage.GetHdc();
                    BitBlt(dcImage, 0, 0, width, height, dcCtrl, 0, 0, 13369376);

                    graphicsCtrl.ReleaseHdc(dcCtrl);
                    graphicsImage.ReleaseHdc(dcImage);
                }
            }
            
            return myImage;
        }

        /// <summary>
        /// Draws the simulation time information on the image.
        /// </summary>
        /// <param name="image"></param>
        private void AddSimulationTimeToImage(Image image)
        {
            using (Graphics graphics = Graphics.FromImage(image))
            {
                // Create font and brush.
                using (Font drawFont = new Font("Arial", 15))
                using (SolidBrush drawBrush = new SolidBrush(Color.White))
                {
                    // Create point for upper-left corner of drawing.
                    PointF drawPoint = new PointF(5.0F, 5.0F);

                    // Draw string on image.
                    graphics.DrawString(string.Format(CultureInfo.CurrentCulture, "t={0:0.00}", this.simulationTime),
                        drawFont, drawBrush, drawPoint);
                }

            }
        }

        #endregion Image Capture

        #endregion Private Helper Methods
    }
}